//  Copyright 2011 Charlie Garrett
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
//
//
//  Instruction.h
//

#ifndef INSTRUCTION_H
#define INSTRUCTION_H

#include <string>
#include <utility>

#include "logic.h"
#include "Register.h"
#include "Term.h"
#include "Value.h"

namespace logic {
    using namespace std;
    
    class Interpreter;
    
    // Instructions modify the state of the WAM machine to perform goal-directed
    // search with unification.
    class Instruction {
    public:
        typedef pair<Functor, size_t> Label;
        
    private:
        Label label;
        
    public:
        Instruction() : label(Functor::Undefined, 0UL) {}

        bool HasLabel() {
            return (!label.first.Equals(Functor::Undefined) ||
                    label.second != 0UL);
        }
        void SetLabel(const Label lbl) {
            label = lbl;
        }
        
        // Return register used by instruction or UNALLOCATED if none.
        virtual Register GetRegister();
        virtual bool Execute(Interpreter *interpreter) = 0;
        virtual string ToString();
        virtual bool ResolveLabel(CodeGenerator *codegen) {
            return true;
        }
    };
        
    // Put Instructions
    
    // Put an unbound REF cell onto the heap and set two registers to
    // refer to it.
    class PutVariable : public Instruction {
    private:
        Register reg;
        Register arg_reg;
        
    public:
        PutVariable(Register x, Register a) : Instruction(), reg(x), arg_reg(a) {}
        
        Register GetRegister() {
            return reg;
        }
        
        Register GetArgumentRegister() {
            return arg_reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Copy a value from a register into an argument register.
    class PutValue : public Instruction {
    private:
        Register reg;
        Register arg_reg;
        
    public:
        PutValue(Register x, Register a) : Instruction(), reg(x), arg_reg(a) {}
        
        Register GetRegister() {
            return reg;
        }
        
        Register GetArgumentRegister() {
            return arg_reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Put a new functor cell onto the heap and set register to an
    // STR cell pointing to that functor cell.
    class PutStructure : public Instruction {
    private:
        Functor functor;
        Register reg;
        
    public:
        PutStructure(Functor &f, Register r) : Instruction(), functor(f), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Put a list cell into an argument register.
    class PutList : public Instruction {
    private:
        Register reg;
        
    public:
        PutList(Register r) : Instruction(), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Put a constant (functor of arity 0) into a register.
    class PutConstant : public Instruction {
    private:
        Value constant;
        Register reg;
        
    public:
        PutConstant(const Value &c, Register r) : Instruction(), constant(c), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    
    // Get Instructions
    
    // Copy a value from an argument register to a normal register.
    class GetVariable : public Instruction {
    private:
        Register reg;
        Register arg_reg;
        
    public:
        GetVariable(Register x, Register a) : Instruction(), reg(x), arg_reg(a) {}
        
        Register GetRegister() {
            return reg;
        }
        
        Register GetArgumentRegister() {
            return arg_reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Unify the values in a normal register and an argument register.
    class GetValue : public Instruction {
    private:
        Register reg;
        Register arg_reg;
        
    public:
        GetValue(Register x, Register a) : Instruction(), reg(x), arg_reg(a) {}
        
        Register GetRegister() {
            return reg;
        }
        
        Register GetArgumentRegister() {
            return arg_reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Expect register to unify with functor.  If the register refers to
    // an unbound REF cell, then it can always unify, so store functor on
    // the heap and set mode to write.  If the register refers to a
    // structure with the correct functor, then set mode to read and
    // continue.  Otherwise, fail because unification is impossible.
    class GetStructure : public Instruction {
    private:
        Functor functor;
        Register reg;
        
    public:
        GetStructure(Functor &f, Register r) : Instruction(), functor(f), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Expect register to unify with a list.  If the register refers to
    // an unbound REF cell, then it can always unify, so set the register
    // to a LIST cell pointing to the top of the heap and set mode to write.
    // If the register refers to a list already, then set mode to read and
    // continue.  Otherwise, fail because unification is impossible.
    class GetList : public Instruction {
    private:
        Register reg;
        
    public:
        GetList(Register r) : Instruction(), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Expect register to unify with a constant.  If the register refers to
    // an unbound REF cell, then it can always unify, so set the register to
    // a CONSTANT cell.  Otherwise, check that the register refers to the
    // desired constant.
    class GetConstant : public Instruction {
    private:
        Value constant;
        Register reg;
        
    public:
        GetConstant(const Value &c, Register r) : Instruction(), constant(c), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    
    // Set Instructions
    
    // Put an unbound REF cell on the heap and set reg to refer to it.
    class SetVariable : public Instruction {
    private:
        Register reg;
        
    public:
        SetVariable(Register r) : Instruction(), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Put the value in reg onto the heap.
    class SetValue : public Instruction {
    private:
        Register reg;
        
    public:
        SetValue(Register r) : Instruction(), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Put the constant value (functor of arity 0) onto the heap.
    class SetConstant : public Instruction {
    private:
        Value constant;
        
    public:
        SetConstant(const Value &c) : Instruction(), constant(c) {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Put N unbound REF cells on top of the heap.
    // Put the value in reg onto the heap.
    class SetVoid : public Instruction {
    private:
        size_t N;
        
    public:
        SetVoid(size_t N_) : Instruction(), N(N_) {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    
    // Unify Instructions
    
    // In read mode, set reg to the contents of the heap at location S
    // (the unification cursor).  In write mode, put a new unbound REF
    // cell on the heap and set reg to refer to it, as in SetVariable.
    class UnifyVariable : public Instruction {
    private:
        Register reg;
        
    public:
        UnifyVariable(Register r) : Instruction(), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // In read mode, unify reg and the contents of the heap at location S.
    // In write mode, put the value in reg onto the heap, as in SetValue.
    class UnifyValue : public Instruction {
    private:
        Register reg;
        
    public:
        UnifyValue(Register r) : Instruction(), reg(r) {}
        
        Register GetRegister() {
            return reg;
        }
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // In read mode, unify a constant and the contents of the heap at location S.
    // In write mode, put the constant onto the heap.
    class UnifyConstant : public Instruction {
    private:
        Value constant;
        
    public:
        UnifyConstant(const Value &c) : Instruction(), constant(c) {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // In read mode, unify skip the next N cells after location S.
    // In write mode, put N unbound reference cells onto the heap, as in SetVoid.
    class UnifyVoid : public Instruction {
    private:
        size_t N;
        
    public:
        UnifyVoid(size_t N_) : Instruction(), N(N_) {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Control instructions
    
    // Invoke the code for a given functor.
    class Call : public Instruction {
    private:
        Functor functor;
        size_t code_offset;
        
    public:
        Call(Functor& f) : Instruction(), functor(f), code_offset(0) {}
        
        bool ResolveLabel(CodeGenerator *codegen);
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Compiled code terminator for a fact.
    class Proceed : public Instruction {
    public:
        Proceed() : Instruction() {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Stop execution.
    class Halt : public Instruction {
    public:
        Halt() : Instruction() {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Allocate a stack frame.
    class Allocate : public Instruction {
    private:
        size_t num_permanent_vars;
        
    public:
        Allocate(size_t n) : Instruction(), num_permanent_vars(n) {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Dellocate a stack frame.
    class Deallocate : public Instruction {
    public:
        Deallocate() : Instruction() {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
    
    // Create a choice point on the stack and store the code offset of
    // the next clause to try if this one fails.
    class TryMeElse : public Instruction {
    private:
        Functor functor;
        size_t next_clause_offset;
    public:
        TryMeElse(const Functor &f) : Instruction(), functor(f), next_clause_offset(0) {}
        
        bool ResolveLabel(CodeGenerator *codegen);
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Reset the state of a choice point, then store the code offset of
    // the next clause to try if this one fails.
    class RetryMeElse : public Instruction {
    private:
        Functor functor;
        size_t clause_num;
        size_t next_clause_offset;
    public:
        RetryMeElse(const Functor &f, size_t cn) : Instruction(), functor(f), clause_num(cn), next_clause_offset(0) {}
        
        bool ResolveLabel(CodeGenerator *codegen);
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Reset the state of a choice point before trying the last clause
    // for a goal.
    class TrustMe : public Instruction {
    private:
    public:
        TrustMe() : Instruction() {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };

    // Associate a heap cell with a source level variable
    // so we can read out the result of a computation.
    class LinkSourceVariable : public Instruction {
    private:
        Variable *var;
        Register reg;
    public:
        LinkSourceVariable(Variable *_var, Register _reg) : Instruction(), var(_var), reg(_reg) {}
        
        bool Execute(Interpreter *interpreter);
        
        string ToString();
    };
}  // namespace logic

#endif   // INSTRUCTION_H

